package com.waves.core.handle;

import com.waves.core.constant.MDCKey;
import com.waves.core.constant.RedisKey;
import com.waves.core.frequencycontrol.enums.HttpErrorEnum;
import com.waves.core.utils.JwtUtils;
import com.waves.core.utils.RedisUtils;
import com.waves.core.web.domain.entities.User;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.Optional;

/**
 * token拦截器
 */
@Order(1)
@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {

    public static final ThreadLocal<User> userInfo = new ThreadLocal<>();

    public static final String AUTHORIZATION_HEADER = "Authorization";

    public static final String AUTHORIZATION_SCHEMA = "Bearer ";

    public static final String ATTRIBUTE_UID = "uid";

    @Resource
    private JwtUtils jwtUtils;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = getToken(request);
        log.info("token===={}", token);
        if (token == null) {
            if ("/task/page".equals(request.getRequestURI()) || request.getRequestURI().startsWith("task/page?type=5")) {
                userInfo.set(new User());
                return true;
            } else {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return false;
            }
        }
        // 校验token是否符合条件，符合返回userId
        Long userId = verify(token);
        try {
            request.setAttribute(ATTRIBUTE_UID, userId);
            if (Objects.isNull(userId)) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                // 判断是不是公共方法，未登录也可以访问
                boolean isPublicURI = isPublicURI(request.getRequestURI());
                if (isPublicURI) {
                    return true;
                }
                HttpErrorEnum.ACCESS_DENIED.sendHttpError(response);
                return false;
            }
            // 从redis中获取用户信息
            String key = RedisKey.getKey(RedisKey.USER_INFO_STRING, userId);
            User user = RedisUtils.get(key, User.class);
            if (user == null) {
                userInfo.set(new User().setId(userId));
            } else {
                userInfo.set(user);
            }
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        } finally {
            MDC.put(MDCKey.UID, String.valueOf(userId));
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        userInfo.remove();
        MDC.remove(MDCKey.UID);
    }

    /**
     * 校验token，并返回userId
     *
     * @param token
     * @return
     */
    private Long verify(String token) {
        Long uid = jwtUtils.getUidOrNull(token);
        if (uid == null) {
            return null;
        }
        String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid);
        String realToken = RedisUtils.getStr(key);
        // token可能失效，需要校验是不是和最新token一致
        if (Objects.equals(token, realToken)) {
            return uid;
        }
        return null;
    }

    /**
     * 判断是不是公共方法，可以未登录访问的
     *
     * @param requestURI
     */
    private boolean isPublicURI(String requestURI) {
        String[] split = requestURI.split("/");
        return split.length > 2 && "public".equals(split[3]);
    }

    /**
     * 从请求头上获取token
     *
     * @param request
     * @return String
     */
    private String getToken(HttpServletRequest request) {
        String header = request.getHeader(AUTHORIZATION_HEADER);
        return Optional.ofNullable(header)
                .filter(h -> h.startsWith(AUTHORIZATION_SCHEMA))
                .map(h -> h.substring(AUTHORIZATION_SCHEMA.length()))
                .orElse(null);
    }

}